Profiling is a general term for techniques that allow software developers to collect data on various characteristics of a running program (a “target program”). The collected data can then be used to understand what parts of the target program need improvement. For example, the developer may measure how much time the target program takes to execute each subroutine and how many times the target program calls each subroutine.
With this information, the developer can determine in which subroutine(s) the target program spends most of its execution time. The developer can then direct optimization first to the most frequently exercised subroutines.
One popular profiling technique is called code instrumentation. When code instrumentation is used to profile a target program, the developer inserts (or “injects”) special sequences of code in selected areas in the target program.
For example, the developer may inject a small piece of start code that starts a clock or timer counting before the very first instruction of a subroutine. The developer may than also inject a similar piece of stop code that stops the clock or timer immediately before each instruction of the subroutine that returns control back to another part of the target program.
As a more specific example, the start code and the stop code may be calls to special user-supplied “methodEntry” and “methodExit” routines. The methodEntry routine notes the current system time and stores its value (e.g., T0) in a memory record created for the selected subroutine in the target program. Just before the selected subroutine exits, the selected subroutine calls the methodExit routine. The methodExit routine then obtains the current system time (e.g., T1), calculates the difference (T1−T0) and stores the difference as a final result in the record for the selected subroutine.
In a similar fashion, the developer may inject profiling code at other interesting places in the target program. Thus, the developer may inject profiling code where the target program allocates objects in memory, performs input/output, and the like. The developer can manually inject the code, or may instead use a system application often referred to as a “profiler”. A profiler typically also supports controlled execution of the target program as well as presentation of the collected data.
Code instrumentation based profiling suffers from a major disadvantage, however: performance overhead. The injected profiling code in the target program necessarily slows down the target program. The slow down can be very significant, often reducing the execution speed of the target program several times.
One reason for the slow down is that injected profiling code needs time to execute. Another, more subtle, reason is that the injected profiling code in the target program may often preclude optimizations that a compiler could otherwise perform on the target program. As one example, a short subroutine can, in many cases, be optimized by replacing the subroutine call (and the attendant subroutine call overhead) with the actual subroutine code instead (i.e., “inlining” the subroutine).
However, the presence of profiling code in the short subroutine makes the subroutine much longer. With increased length comes increased unsuitability for inlining. Thus, optimization opportunities may be lost.
Therefore, a need has long existed for code instrumentation profiling of target programs that overcomes the problems noted above and others previously experienced.